#include <dlfcn.h>
#include <sys/time.h>
#include <stdlib.h>

#include "CPU.h"
#include "base/CompletionRequest.h"
#include "base/Configuration.h"
#include "base/GDB.h"
#include "base/LoadRequest.h"
#include "base/Processor.h"
#include "base/QuiesceRequest.h"
#include "base/QuiesceResponse.h"
#include "base/RawFlushRequest.h"
#include "base/Response.h"
#include "base/Simulator.h"
#include "base/StoreRequest.h"
#include "base/UnQuiesceRequest.h"
#include "base/isa_ext.h"
#include "base/signals/SigManager.h"
#include "base/signals/SigHandler.h"
#include "base/simconstants.h"
#include "mem/UpdateRequest.h"
#include "mem/VerifyRequest.h"
#include "mem/RawMemoryRequest.h"

NEW_DELETE_OPERATORS_CPP(Processor,100);
NEW_DELETE_OPERATORS_CPP(Access,1000000);

bool Processor::initializedClass = false;
void Processor::initializeClass ()
{
    if (initializedClass) {
        return;
    }
    initializedClass = true;

    global_sim->register_isa_ext(ISA_EXT_GETMYID,
                                 &Processor::isa_ext_get_myid);
    global_sim->register_isa_ext(ISA_EXT_GOTO_REAL,
                                 &Processor::isa_ext_goto_real);
    global_sim->register_isa_ext(ISA_EXT_PRINT_FMT,
                                 &Processor::isa_ext_set_print_fmt);
    global_sim->register_isa_ext(ISA_EXT_PRINT_ARGV,
                                 &Processor::isa_ext_set_print_argv);
    global_sim->register_isa_ext(ISA_EXT_PRINT_WARNING,
                                 &Processor::isa_ext_set_print_warning);
    global_sim->register_isa_ext(ISA_EXT_PRINT_ARGC,
                                 &Processor::isa_ext_print_atomic);
    global_sim->register_isa_ext(ISA_EXT_PRINT_BUFFER,
                                 &Processor::isa_ext_print_atomic_buffer);
    global_sim->register_isa_ext(ISA_EXT_SETPRINT,
                                 &Processor::isa_ext_set_print);
    global_sim->register_isa_ext(ISA_EXT_SET_IGNORE_PROGRESS,
                                 &Processor::isa_ext_set_ignore_progress);
    global_sim->register_isa_ext(ISA_EXT_SETJMP,
                                 &Processor::isa_ext_setjmp);
    global_sim->register_isa_ext(ISA_EXT_LONGJMP,
                                 &Processor::isa_ext_longjmp);

    // For stats
    global_sim->register_isa_ext(ISA_EXT_STAT_LOCK,
                                 &Processor::isa_ext_stat_lock);
    global_sim->register_isa_ext(ISA_EXT_STAT_UNLOCK,
                                 &Processor::isa_ext_stat_unlock);
    global_sim->register_isa_ext(ISA_EXT_STAT_BAR,
                                 &Processor::isa_ext_stat_bar);
    global_sim->register_isa_ext(ISA_EXT_STAT_BAREND,
                                 &Processor::isa_ext_stat_bar_end);
    global_sim->register_isa_ext(ISA_EXT_STATS_ENABLE,
                                 &Processor::isa_ext_stat_enable);
    global_sim->register_isa_ext(ISA_EXT_STATS_DISABLE,
                                 &Processor::isa_ext_stat_disable);

    global_sim->register_isa_ext(ISA_EXT_LINE_MOVE,
                                 &Processor::isa_ext_line_move);
}

Processor::Processor (Simulator* simulator, const char* name, uint id) :
    Entity(name, CLOCK_WITH_PROCESSORS),
    hb_print(false),
    hb_ival(0),
    ips_print(false),
    ips_ival(0),
    firstCycle(true),
    print_warning(true),
    status(halted),
    _id(id),
    stack_unaligned_start(buildStack()),
    cpu(NEW CPU(this, stack_aligned_end)),
    jmpbufPtr(NEW Checkpoint()),
    jmpval(0),
    sigHandler(NEW SigHandler(cpu)),
    trapGenerator(NEW TrapGenerator()),
    watchdogIgnoreProgress(false),
    watchdogLastPC(0),
    watchdogLastSimclock(0),
    _quiesce(false),
    _quiesceAfterThisInstruction(false),
    _quiescedActive(false)

{
    initializeClass();

    registerPacketHandler(typeid(Response),
                          (PacketHandlerPtr)&Processor::handle_Response);
    registerPacketHandler(typeid(RawFlushRequest),
                          (PacketHandlerPtr)&Entity::routePacket);
    registerPacketHandler(typeid(InvalidateRequest),
                          (PacketHandlerPtr)&Entity::routePacket);
    registerPacketHandler(typeid(VerifyRequest),
                          (PacketHandlerPtr)&Entity::routePacket);
    registerPacketHandler(typeid(UpdateRequest),
                          (PacketHandlerPtr)&Entity::routePacket);
    registerPacketHandler(typeid(QuiesceRequest),
                          (PacketHandlerPtr)&Processor::handle_QuiesceRequest);
    registerPacketHandler(typeid(UnQuiesceRequest),
                          (PacketHandlerPtr)&Processor::handle_UnQuiesce);
    registerPacketHandler(typeid(QuiesceResponse),
                          (PacketHandlerPtr)&Processor::handle_QuiesceResponse);

    // statistics and profiliing
    initStats();
}

Processor::~Processor () {
    delete cpu;
    delete programLog;
    delete sigHandler;
    delete trapGenerator;
    delete itrace;
    delete statistics;
    delete jmpbufPtr;
    FREE(stack_unaligned_start);
}

Byte* Processor::buildStack () {
    
    // we need to getconfig here to get stack options
    getconfig();

    // allocate stack
    size_t size = cfg.initial_stack_size + cfg.stack_alignment_size;
    Byte* stack_unaligned_start = (Byte*)CALLOC(size, sizeof(Byte));
    Byte* stack_unaligned_end   = stack_unaligned_start+size;
    unsigned int stack_alignment_mask = (cfg.stack_alignment_size-1) ^ (uint)-1;
    Byte* stack_aligned_end = (Byte*)((unsigned int)stack_unaligned_end & stack_alignment_mask);
    if (cfg.stack_debug) {
        Log->i("-- Processor %d's stack-------------\n", getID());
        Log->i("initial_stack_size   =%d\n",   cfg.initial_stack_size);
        Log->i("stack_alignment_size =%d\n",   cfg.stack_alignment_size);
        Log->i("stack_alignment_mask =%08x\n", stack_alignment_mask);
        Log->i("stack_unaligned_start=%p\n",   stack_unaligned_start);
        Log->i("stack_unaligned_end  =%p\n",   stack_unaligned_end);
        Log->i("stack_aligned_end    =%p\n",   stack_aligned_end);
    }
    ASSERT(stack_unaligned_start < stack_aligned_end &&
           stack_aligned_end < stack_unaligned_end);
    this->stack_aligned_end = stack_aligned_end;
    return stack_unaligned_start;
}

void Processor::initStats () {

    char tmpString[128];
    sim_sprintf(tmpString, "Processor: %d", getID());
    statistics = NEW StatisticsCollection(tmpString);
    stats_lock_nest = 0;

    ctrStatsEnable = statistics->addCounter("stats-enable");

    //statsStallTime = statistics->addCounter("Stall-Time");
    statInstructionsExecuted = statistics->addCounter("Instructions-Executed");
    statUnaccounted = statistics->addCounter("Cycles-Unaccounted");
    statistics->addCounter("L1-Accesses");
    statistics->addCounter("L1-Hits");
    statistics->addCounter("L1-Misses");
    statistics->addCounter("L1-Accesses-By-Log");
    statistics->addCounter("L1-Hits-By-Log");
    statistics->addCounter("L1-Misses-By-Log");

    //
    // ORDER MATTERS WITH BUCKETS!
    //
    bktCyclesHalted        = statistics->addBucket("Cycles-Halted");
    bktCyclesBusy          = statistics->addBucket("Cycles-Busy");
    statistics->addBucket("Cycles-L1-Unavailable");


    bktMemoryStall   = statistics->addBucket("Cycles-L1-Stall");
    statistics->addBucket("Cycles-Memory-Stall");

    bktCyclesBarrier = statistics->addBucket("Cycles-Barrier");
    bktCyclesLock    = statistics->addBucket("Cycles-Lock");

    bktCyclesHalted->vote(); //begin halted
}

// look for duplicated code
bool Processor::cancelDataRequest () {
    if (dataRequest != NULL) {
        bktMemoryStall->unvote();
        bktCyclesBusy->vote();

        clearDataRequest();
        repeater.clear();

        // Processor could have non-NULL dataRequest, but could
        // already be active. An example of when this could happen is
        // when a load request is finished and the processors is
        // activated the same cycle that the processor is violated.
        if (!global_sim->isActive(this)) {
            global_sim->activate(this);
        }
        return true;
    }
    
    return false;
}

void Processor::clearDataRequest () {
    delete dataRequest;
    dataRequest = NULL;
    dataRequestDone = false;
    dataRequestFailed = false;
    firstCycle = true;
}

void Processor::rawFlushCaches (VA start, size_t len) {
    RawFlushRequest* r = NEW RawFlushRequest(this,
                                             global_sim->getMemory(),
                                             start,
                                             len);
    handle(r);
    delete r;
}

void Processor::getconfig () {
    cfg.initial_stack_size = global_cfg->getIntegerArgument("initial_stack_size",
                                                            "the initial size of the stack(s)",
                                                            256*1024,
                                                            CONFIG_MIN,
                                                            256*1024);
    cfg.stack_alignment_size = global_cfg->getIntegerArgument("stack_alignment_size",
                                                              "size of chunk to align stack(s) to",
                                                              16,
                                                              CONFIG_MIN,
                                                              4);
    cfg.stack_debug = global_cfg->getBooleanArgument("stack_debug",
                                                     "print stack debug information");

    cfg.print_master = global_cfg->getBooleanArgument("itrace", "Turn on instruction tracing.");

    const char* format = global_cfg->getStringArgument("itrace_format",
                                                       "Format of the inst trace. [fcmrRs]",
                                                       "fmrs");

    if (strcmp(format, "n")) {
        bool split_trace = global_cfg->getBooleanArgument("split_itrace", "Split the inst trace per processor");
        if (split_trace) {
            int id = getID();
            char cfg_name[100];
            char cfg_desc[100];
            char cfg_file[100];
            sim_snprintf(cfg_name, 100, "itrace_%d", id);
            sim_snprintf(cfg_desc, 100, "Instruction trace for p%d", id);
            sim_snprintf(cfg_file, 100, "itrace.p%d", id);
            itrace = NEW Logger(cfg_name, "i", cfg_file, cfg_desc);
        } else {
            itrace = NEW Logger("itrace_file", "i", "log.sim", "Instruction trace");
        }
    } else {
        // this is so the memory layout is the same with or without itracing
        itrace = NEW Logger("itrace_off", "", "log.sim:e", "This shouldn't show up.", false);
    }
    
    cfg.print_inst_on_commit = (bool)strchr(format, 'c');
    cfg.print_inst_on_fetch = (bool)strchr(format, 'f');
    cfg.print_all_registers = (bool)strchr(format, 'R');
    cfg.print_registers = cfg.print_all_registers | (bool)strchr(format, 'r');
    cfg.print_memory = (bool)strchr(format, 'm');
    cfg.print_symbols = (bool)strchr(format, 's');
    cfg.transaction_trace = (bool)strchr(format, 't');

    if ((hb_print = global_cfg->getBooleanArgument("heartbeat", "Print heartbeat.", true))) {
        hb_ival = global_cfg->getIntegerArgument("heartbeat_ival",
                                                 "Number of instructions between heartbeats.",
                                                 1024*1024,
                                                 CONFIG_UNSIGNED);
        last_inst = 0;
    }

    if ((ips_print = global_cfg->getBooleanArgument("ips", "Print instructions per second.", true))) {
        ips_ival = global_cfg->getIntegerArgument("ips_ival",
                                                    "Number of seconds between printing IPS.",
                                                    60,
                                                    CONFIG_UNSIGNED);
        last_ips_inst = 0;
        last_ips_sec = 0;
    }

    // sample_delta, if defined, causes processor state (bucket) to
    //   be sampled every <sample_delta> cycles
    // used for visualization/graphing purposes
    sample_delta = 
        global_cfg->getIntegerArgument("sample",
                                       "Sample state every N sim_cycles.  Supply N.",
                                       -1 /*default*/);

    programLog = NEW Logger("programLog", "sim", "-:w,log.sim", "program output");
}

void Processor::reset (VA startPC,
                       VA returnPC,
                       int argc,
                       Reg arg0,
                       Reg arg1,
                       Reg arg2,
                       Reg arg3)
{
    cpu->reset(startPC, returnPC, argc, arg0, arg1, arg2, arg3);
    bktCyclesHalted->unvote();
    bktCyclesBusy->vote();
    status = running;
    watchdogTimeSinceProgress(); // just reset the counters
    global_sim->activate(this);
}

Result Processor::handle_QuiesceResponse (Packet* r) {
    quiesceLog->d("%d:%lli: Processor::handle_QuiesceResponse\n", getID(), global_sim->getSimclock());
    // ok, my caches are done w/ their requests...
    global_sim->quiesced(this);
    
    hookQuiesced();
    
    delete ((QuiesceResponse*)r)->getRequest();
    delete r;
    return success;
}

void Processor::hookQuiesced () {
}

bool Processor::isQuiescing () {
    return _quiesce;
}

void Processor::hookQuiesce () {
    setQuiesceAfterThisInstruction(true);
}

Result Processor::handle_QuiesceRequest (Packet* r) {
    delete r;
    ASSERT(getQuiesce() == false);
    setQuiesce(true);
    setQuiesceAfterThisInstruction(false);

    bool readyToQuiesce = isReadyToQuiesce();
    quiesceLog->d("%s: Processor::handle_QuiesceRequest: isActive=%i dataRequest=%p(addr:%llx) isReadyToQuiesce=%d status=%s\n",
                  name,
                  global_sim->isActive(this),
                  dataRequest,
                  dataRequest ? dataRequest->getAddress() : 0,
                  readyToQuiesce,
                  getStatus());

    if (readyToQuiesce) {
        sendQuiesceRequest();
    } else {
        hookQuiesce();
    }

    return success;
}

bool Processor::isReadyToQuiesce () {
    // if this guy is not active and he doesnt have an outstanding
    // request, then he wont be active in the future, which means
    // he wont run the tick() code to send the quiesce request to the
    // lower levels. so send it now

    // jw : the above is not always true.
    // for example, a core sends a request (not data request) down
    // to the cache. it can be commit request or abort request.
    // a processor is deactived after sending the request and
    // activated when it gets the response of the request.
    // isReadyToQuiesce() is added to check if a processor
    // can be quiesced right now.
    
    return (isHalted() ||
            (!global_sim->isActive(this) && dataRequest == NULL));
}

Result Processor::handle_UnQuiesce (Packet* r) {
    delete r;
    ASSERT(getQuiesce() == true);
    setQuiesce(false);

    quiesceLog->d("%d: handle_UnQuiesce: quiescedActive: %i\n",
                  getID(),
                  getQuiescedActive());

    if (getQuiescedActive()) {
        global_sim->activate(this);
        clearRepeater();
    }

    return success;
}

Result Processor::handle_Response (Packet* r) {
    ASSERT(((Response*)r)->getRequest() == dataRequest);
    if ( ((Response*)r)->getFlag() != RESPONSE_DONE) {
        dataRequestFailed = true;
    }
    delete r;

    global_sim->activate(this);

    bktMemoryStall->unvote();
    bktCyclesBusy->vote();
    dataRequestDone = true;

    return success;
}

void deleteAccess (VA addr, Access* a) {
    delete a;
}

void Processor::clearRepeater() {
    repeater.clear();
}

bool Processor::isRepeating () {
    return repeater.isRegistered();
}

Result Processor::sendWordAccess (VA addr,
                                  uint32* res,
                                  WordAccessType type,
                                  bool atomic)
{
    Result result;

    if (repeater.isRegistered()) {
        // we've seen this access before while servicing this instruction:
        // grab the value instead of bothering the cache
        //
        switch (type) {
          case WORDACCESS_LOAD:
          case BLOCKACCESS_LOAD:
            repeater.getValue(addr, type, res);
            break;
          case BLOCKACCESS_STORE:
          case WORDACCESS_STORE:
          case DWORDACCESS_STORE:
          case QWORDACCESS_STORE:
            repeater.skip();
            break;
          default:
            ASSERT(false);
        }
 
        return success;
    }

    Result tlbAccess = accessTLB(addr, type);
    if(tlbAccess != success) {
        return tlbAccess;
    }

    // first time through...after we issue the request to the
    // underlying memory heirarchy, we pull ourselves off the
    // run queue...
    //
    ASSERT(dataRequest == NULL || dataRequest->getAddress() == addr);
    if (dataRequest == NULL) {
        switch (type) {
          case WORDACCESS_LOAD:
            dataRequest = NEW LoadRequest(addr,
                                          this,
                                          this,
                                          global_sim->getMemory(),
                                          WORD_SIZE);
            break;

          case WORDACCESS_STORE:
            dataRequest = NEW StoreRequest(addr,
                                           this,
                                           this,
                                           global_sim->getMemory(),
                                           WORD_SIZE);
            dataRequest->setWord(*res);
            break;

          case DWORDACCESS_STORE:
            dataRequest = NEW StoreRequest(addr,
                                           this,
                                           this,
                                           global_sim->getMemory(),
                                           WORD_SIZE*2);
            dataRequest->insertData(res, WORD_SIZE*2);
            break;

          case QWORDACCESS_STORE:
            dataRequest = NEW StoreRequest(addr,
                                           this,
                                           this,
                                           global_sim->getMemory(),
                                           WORD_SIZE*4);
            dataRequest->insertData(res, WORD_SIZE*4);
            break;

          case BLOCKACCESS_LOAD:
            dataRequest = NEW LoadRequest(addr,
                                          this,
                                          this,
                                          global_sim->getMemory(),
                                          global_sim->cfg.blocksize);
            break;

          case BLOCKACCESS_STORE:
            dataRequest = NEW StoreRequest(addr,
                                           this,
                                           this,
                                           global_sim->getMemory(),
                                           global_sim->cfg.blocksize);
            dataRequest->insertData(res, global_sim->cfg.blocksize);
            break;

          default:
            Log->e("Uknown WordAccess type: %d", (int)type);
        }

        dataRequestDone = false;
        dataRequestFailed = false;
        dataRequest->setExclusive(atomic);

#if 0
        traceLog->d("%012lli P%02i %s:%016llx @%016llx\n",
                    global_sim->getSimclock(),
                    getID(),
                    type == WORDACCESS_LOAD ? "LD" :
                        (type == WORDACCESS_STORE ? "ST" : "??"),
                    addr,
                    cpu->getPC());
#endif

        hookWordAccess();

        // here we deactive the processor first and then send the request.
        // this is for cache hit at L1 where the response is sent back to
        // the processor before routePacket() returns.
        // in this case, activate()/deactivate() order is messed up.

        bktCyclesBusy->unvote();

        // we need this "if" here because when we overflow while
        // another processor is trying to quiesce us, we might find
        // that we were already deactivated in the violation code.
        if (global_sim->isActive(this)) {
            global_sim->deactivate(this);
        }

        // usually this takes less than one cycle but
        // it may take more so we count with the
        // Memory-Stall bucket
        bktMemoryStall->vote();
        result = routePacket(dataRequest);

        if (result == success) {
            if (dataRequestDone) { // it could be done in 1 cycle...
                goto accessSuccessful;
            }
            return stall;
        } else {
            // the result can be stall or failure.
            // if it is stall, then try it at the next tick.
            // if it is failure, then be careful not to send Response() with
            // failure at the same time. if a module wants to send response to
            // let the processor know an abnormal situation, it should return
            // success first meaning yes i got the request well.  then use
            // reponse.
            cancelDataRequest();
            return result;
        }
    } else if (dataRequestDone) { // we need to have a request and it be done
        // the request is done...
        // (we know because we've been put back on the run queue,
        //  and dataRequest has already been initialised)

accessSuccessful:
        // check first the memory access failed.
        // this happens with Atomic memory access instructions.
        if (dataRequestFailed) {
            Log->d("%i:%lli:%0llx REQUEST FAILED\n",
                   getID(),
                   global_sim->getSimclock(),
                   dataRequest->getAddress());
            cancelDataRequest();
            return failure;
        }

        switch (type) {
          case WORDACCESS_LOAD:
            *res = *((uint32*)dataRequest->getData());
            break;

          case WORDACCESS_STORE:
          case DWORDACCESS_STORE:
          case QWORDACCESS_STORE:
            break;

          case BLOCKACCESS_LOAD:
            ASSERT(dataRequest->getDataSize() == global_sim->cfg.blocksize);
            memcpy(res, dataRequest->getData(), global_sim->cfg.blocksize);
            break;

          case BLOCKACCESS_STORE:
            break;

          default:
            Log->e("Unknown WordAccess type: %d\n", (int)type);
        }

        if (cfg.print_master &&
            cfg.print_memory &&
            global_sim->cfg.stats_enabled &&
            global_sim->isWatched(addr))
        {
            // make sure word aligned (lower two bits zero)
            ASSERT((addr & 3) == 0);
            uint32* vAddr = ADDR_UINT32STAR(addr);
            if (type == WORDACCESS_LOAD) {
                getInstructionTrace()->i(
                    "%d:%lld:%.16llX LOAD | 0x%x %d = *%p\n",
                    getID(), global_sim->getSimclock(), cpu->getPC(),
                    *res, *res, vAddr);
            }
            else if (type == WORDACCESS_STORE) {
                getInstructionTrace()->i(
                    "%d:%lld:%.16llX STORE| *%p = 0x%x %d\n",
                    getID(), global_sim->getSimclock(), cpu->getPC(),
                    vAddr, *res, *res);
            }
            else if (type == DWORDACCESS_STORE) {
                getInstructionTrace()->i(
                    "%d:%lld:%.16llX STORE| *%p = 0x%llx %lld\n",
                    getID(), global_sim->getSimclock(), cpu->getPC(),
                    vAddr, *((uint64*)res), *((uint64*)res));
            }                    
            else if (type == QWORDACCESS_STORE) {
                getInstructionTrace()->i(
                    "%d:%lld:%.16llX STORE| *%p = 0x%llx_%llx\n",
                    getID(), global_sim->getSimclock(), cpu->getPC(),
                    vAddr, *((uint64*)res+1), *((uint64*)res));
            }                    
            else if (type == BLOCKACCESS_LOAD) {
                getInstructionTrace()->i(
                    "%d:%lld:%.16llX BLOCK LOAD | *%p\n", 
                    getID(), global_sim->getSimclock(), cpu->getPC(), vAddr);
            }
            else if (type == BLOCKACCESS_STORE) {
                getInstructionTrace()->i(
                    "%d:%lld:%.16llX BLOCK STORE| *%p\n", 
                    getID(), global_sim->getSimclock(), cpu->getPC(), vAddr);
            }
            else {
                getInstructionTrace()->e("Unexpected WORDACCESS_* type %d\n",
                                         type);
            }
        }

        repeater.registerAccess(dataRequest->getAddress(),
                                (uint32*)dataRequest->getData(),
                                type);
        
        hookWordAccessSuccess(type);
        
        clearDataRequest();
        return success;
    }
    return stall;
}

Result Processor::accessTLB(VA addr, WordAccessType type) {
    // jw : before we start a memory access, we need to check the validity
    // of the access. this will be done with TLB and PageTable later.
    // for now, just try the access to the memory in the old less style to
    // check SIGSEGV
    uint32 tmp_TLBCheck = 0;
    tmp_TLBCheck = *ADDR_UINT32STAR(addr);

    // without this noop, gcc -O2 causes SIGSEGV to be avoided for NULL
    // addr
    noop_uint(tmp_TLBCheck);

    return hookTLBAccess(addr, type);
}

// the default tlb always hits and succeeds.
Result Processor::hookTLBAccess(VA addr, WordAccessType type) {
    return success;
}

void Processor::sendQuiesceRequest () {
    QuiesceRequest* qr = NEW QuiesceRequest(this,
                                            global_sim->getMemory());
    quiesceLog->d("%i:%lli:%0llx sending remote QuiesceRequest active(%i)\n",
                  getID(),
                  global_sim->getSimclock(),
                  cpu->getPC(),
                  global_sim->isActive(this));
    Result r = routePacket(qr);
    ASSERT(r == success);

    // record whether or not we're active now...
    setQuiescedActive(global_sim->isActive(this));

    if (getQuiescedActive()) {

        //tayo: removed because this was breaking stats
        //      the processor does not re-enter the busy state 
        //      when the quiesce operation completes

        //getStatistics()->getBucket("Cycles-Halted")->vote();
        //getStatistics()->getBucket("Cycles-Busy")->unvote();

        global_sim->deactivate(this);
    }
}

void Processor::tick () {
    printHeartbeat();
    printIPS();
    if (status == halted) {
        return;
    }
    
    // see if its time to stop in the debugger
    global_sim->getGDB()->debug(getID(), cpu->getPC(), firstCycle);

    repeater.reset();
    bool finished = cpu->fetchDecodeExecuteCommitInstruction();
    if (finished) {
        firstCycle = true;
        repeater.clear();
        if (global_sim->cfg.stats_enabled) {
            statInstructionsExecuted->increment();
        }

        if (getQuiesce() && getQuiesceAfterThisInstruction()) {
            // if were trying to quiesce, then send a request to the cache
            // and wait for the response. when the response comes, then
            // let the simulator know we've quiesced.
            sendQuiesceRequest();
        }

        hookInstructionCompletion();
    } else {
        firstCycle = false;
        //record a stall
        //statsStallTime->increment();
    }
    if (watchdogMadeProgress()) {
        watchdogLastSimclock = global_sim->getSimclock();
    }
}

void Processor::hookInstructionCompletion() {
}

void Processor::halt () {
    bktCyclesHalted->vote();
    bktCyclesBusy->unvote();
    status = halted;
    global_sim->deactivate(this);

    cpu->reset((VA)0,
               ADDR_UINT64((void*)abort),
               0,
               (Reg)SIMARG_UNUSED,
               (Reg)SIMARG_UNUSED,
               (Reg)SIMARG_UNUSED,
               (Reg)SIMARG_UNUSED);
    Log->i("%i:%lli Processor halted\n",
           getID(),
           global_sim->getSimclock());
}

void Processor::countVotes () {
    // elect the winning bucket
    // based on correctly ordered vector; elects the first positive vote
    Vector<Bucket*>* buckets = getStatistics()->getBuckets();

    Vector<Bucket*>::reverse_iterator bktIdx;
    for (bktIdx = buckets->rbegin();
         bktIdx != buckets->rend();
         bktIdx++)
    {
        Bucket* bkt = *bktIdx;
        if (bkt->getVoteStatus()) {
            sampleState(bkt);
            hookElect(bkt);
            return; // only elect one bucket (the first voted one encountered)
        }
    }

    // someone should have voted...figure out why this did not happen
    // austenmc: this should die---if you change this to a debug message just
    // b/c you have unaccounted cycles, Christos will find you, and kill you.
    Log->e("%i:%lli: unaccounted cycle\n", getID(), global_sim->getSimclock());
    statUnaccounted->increment(1);
}

bool Processor::isRunning () {
    return (status == running);
}

bool Processor::isHalted () {
    return (status == halted);
}

bool Processor::canHandleSignal() {
    // right now signal handling can only deal with running processors
    // because we don't know if we should return to a halted state
    // or the state the signal handler sets to, we should be able
    // to handle this.
    if ((status == running) && !sigHandler->isProcessing()) {
        return true;
    }
    return false;
}

bool Processor::watchdogMadeProgress () {
    VA PC = cpu->getPC();
    if (watchdogLastPC != PC || cpu->repInst()) {
        watchdogLastPC = PC;
        return true;
    }
    return false;
}

Time Processor::watchdogTimeSinceProgress () {
    return (global_sim->getSimclock() - watchdogLastSimclock);
}

bool Processor::inPrivateBounds (VA addr, size_t len) {
    // if we are close to current stack pointer, assume
    // private. This is most important for JikesRVM which
    // allocates and runs on its own stacks, not the simulator
    // provided ones
    if ((addr - cpu->getStackPointer()) < 1024) {
        return true;
    }
        
    // check bounds of simulator allocated stack
    if (stack_aligned_end <= (Byte*)(ADDR_UINT32(addr)+len) &&
        (Byte*)ADDR_UINT32(addr) <= stack_unaligned_start) {
        return true;
    }

    if (sigHandler->inSigContextsBounds(addr, len)) { 
        return true;
    }

    // we are not in bounds for flushing
    return true;
}

bool Processor::isa_ext_get_myid (Processor* P,
                                  Reg arg0,
                                  Reg arg1,
                                  Reg* result)
{
    *result = P->getID();
    return true;
}

bool Processor::serialize () {
    static bool quiesced = false;
    quiesceLog->d("%i:%lli goto_real quiesced=%d getQuiesce=%d getQuiesceAfterThisInstruction=%d\n",
                  getID(),
                  global_sim->getSimclock(),
                  quiesced,
                  getQuiesce(),
                  getQuiesceAfterThisInstruction());
    
    // Someone else wants us to quiesce after this
    // instruction. However, since we are also trying to quiesce, our
    // instruction cannot complete. So just give in to the other
    // entity's quiesce request
    if (getQuiesce() && getQuiesceAfterThisInstruction()) {
        sendQuiesceRequest();
        return false;
    }

    // flush and invalidate all caches
    if (!quiesced) {
        global_sim->quiesce(this);
        quiesced = true;
    }
    if (!global_sim->isQuiesced()) {
        quiesceLog->d("%i:%lli goto_real isQuiesced=false\n",
                      getID(),
                      global_sim->getSimclock());

        return false;
    }
    quiesced = false;
    return true;
}

void Processor::unserialize () {
    global_sim->unquiesce(this);
}

bool Processor::isa_ext_goto_real (Processor* P,
                                   Reg arg0,
                                   Reg arg1,
                                   Reg* result)
{
    ASSERT(inSimulation);

    if (!P->serialize()) {
        return false;
    }

    global_sim->rawFlushCaches(0, 0);
    global_sim->invalidateCaches(P, __func__);
    P->unserialize();

    global_sim->getSM()->restoreHandlerRegistration();
    global_sim->getTM()->goto_real();
    inSimulation = false;
    P->cpu->goto_real();
    return true;
}


bool Processor::isa_ext_set_print_fmt (Processor* P,
                                       Reg arg0,
                                       Reg arg1,
                                       Reg* result)
{
    P->print_fmt = (char*)ADDR_UINT32(arg0);
    return true;
}

bool Processor::isa_ext_set_print_argv (Processor* P,
                                        Reg arg0,
                                        Reg arg1,
                                        Reg* result)
{
    P->print_argv[arg1] = ADDR_UINT32(arg0);
    return true;
}

bool Processor::isa_ext_set_print_warning (Processor* P,
                                           Reg arg0,
                                           Reg arg1,
                                           Reg* result)
{
    P->print_warning = ADDR_UINT32(arg0);
    return true;
}

bool Processor::isa_ext_print_atomic (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    int argc = (int)arg0;

    void (Logger::* printFunction)(const char*, ...);

    if (P->print_warning) {
        printFunction = &Logger::warning;
    } else {
        printFunction = &Logger::info;
        P->print_warning = true;
    }

    switch (argc) {
    case 0:
        (P->programLog->*printFunction)(P->print_fmt);
        break;
    case 1:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0]);
        break;
    case 2:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1]);
        break;
    case 3:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1],
                                        P->print_argv[2]);
        break;
    case 4:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1],
                                        P->print_argv[2],
                                        P->print_argv[3]);
        break;
    case 5:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1],
                                        P->print_argv[2],
                                        P->print_argv[3],
                                        P->print_argv[4]);
        break;
    case 6:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1],
                                        P->print_argv[2],
                                        P->print_argv[3],
                                        P->print_argv[4],
                                        P->print_argv[5]);
        break;
    case 7:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1],
                                        P->print_argv[2],
                                        P->print_argv[3],
                                        P->print_argv[4],
                                        P->print_argv[5],
                                        P->print_argv[6]);
        break;
    case 8:
        (P->programLog->*printFunction)(P->print_fmt,
                                        P->print_argv[0],
                                        P->print_argv[1],
                                        P->print_argv[2],
                                        P->print_argv[3],
                                        P->print_argv[4],
                                        P->print_argv[5],
                                        P->print_argv[6],
                                        P->print_argv[7]);
        break;
    case 9: {
        long long a1 = P->print_argv[0];
        long long a2 = P->print_argv[1];
        long long  ll = (a1 <<32 ) | (a2 & 0xFFFFFFFFULL);
        double d = longlong2double(ll);
        (P->programLog->*printFunction)(P->print_fmt, d);
        break;
    }
    default:
        Log->e("print_atomic: argc=%d\n", argc);
    }
    return true;
}



bool Processor::isa_ext_print_atomic_buffer (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    Byte b = (Byte)arg0;
    if (P->print_buffer_index < PRINT_BUFFER_SIZE) {
        P->print_buffer[P->print_buffer_index] = b;
        P->print_buffer_index++;
    }
    if (b == '\0') {
        P->print_buffer[PRINT_BUFFER_SIZE - 1] = '\0';
        if (P->print_warning) {
            P->programLog->warning("%s", P->print_buffer);
        } else {
            P->programLog->info("%s", P->print_buffer);
            P->print_warning = true;
        }
        P->print_buffer_index = 0;
    }
    return true;
}

bool Processor::isa_ext_set_print (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    P->cfg.print_master = (bool)arg0;
    return true;
}

bool Processor::isa_ext_set_ignore_progress (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    P->watchdogIgnoreProgress = (bool)arg0;
    return true;
}

bool Processor::isa_ext_setjmp (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    P->cpu->takeCheckpoint(P->jmpbufPtr);
    *result = (Reg)P->jmpval;
    P->jmpval = 0;
    return true;
}

bool Processor::isa_ext_longjmp (Processor* P, Reg arg0, Reg arg1, Reg* result) {
    while (P->getSigHandler()->isProcessing()) {
        P->getSigHandler()->cleanupState();
    }
    P->jmpval = (int)((arg0 == 0) ? (1) : (arg0));
    P->cpu->restoreCheckpoint(P->jmpbufPtr);
    return true;
}

bool Processor::isa_ext_stat_lock (Processor* P, 
                                   Reg arg0, 
                                   Reg arg1, 
                                   Reg* result) {
    P->incStatsLockNest();
    P->bktCyclesLock->vote();
    return true;
}

bool Processor::isa_ext_stat_unlock (Processor* P, 
                                     Reg arg0, 
                                     Reg arg1, 
                                     Reg* result) {
    int nest_result = P->decStatsLockNest();
    
    if (nest_result > 0) {    // still in nested lock
        return true;
    } 
    if (nest_result == 0) {  // leaving lock region
        P->bktCyclesLock->unvote();
        return true;
    } 
    // trying to leave lock region while not in lock
    Log->e("Attempting to end lock region while not in lock...\n");
    return false;
}

bool Processor::isa_ext_stat_bar (Processor* P, 
                                  Reg arg0, 
                                  Reg arg1, 
                                  Reg* result) {
    if (P->bktCyclesBarrier->getVoteStatus()) {
        Log->e("Cannot nest barriers...\n");
    } 
    P->bktCyclesBarrier->vote();
    return true;
}

bool Processor::isa_ext_stat_bar_end (Processor* P, 
                                      Reg arg0, 
                                      Reg arg1, 
                                      Reg* result) {
    if (!(P->bktCyclesBarrier->getVoteStatus())) {
        Log->e("Attempting to end barrier region while not in barrier...\n");
      
    } 
    P->bktCyclesBarrier->unvote();
    return true;
}

bool Processor::isa_ext_stat_enable (Processor* P, 
                                     Reg arg0, 
                                     Reg arg1, 
                                     Reg* result) {
    global_sim->cfg.stats_enabled = true;
    P->ctrStatsEnable->increment();
    return true;
}

bool Processor::isa_ext_stat_disable (Processor* P, 
                                      Reg arg0, 
                                      Reg arg1, 
                                      Reg* result) {
    global_sim->cfg.stats_enabled = false;
    return true;
}

bool Processor::isa_ext_line_move (Processor* P,
                                   Reg arg0, // src
                                   Reg arg1, // dest
                                   Reg* result) {
    uint buffer[MAX_BLOCK_SIZE/WORD_SIZE];

    Log->d("%i:%lli LINE_MOVE %0llx -> %0llx\n",
           P->getID(),
           global_sim->getSimclock(),
           (VA)arg0,
           (VA)arg1);

    if (P->sendWordAccess(arg0, buffer, BLOCKACCESS_LOAD, false) != success) {
        // trigger reexecution
        Log->d("  LINE_MOVE LOAD PENDING\n");
        return false;
    }
    if (P->sendWordAccess(arg1, buffer, BLOCKACCESS_STORE, false) != success) {
        // trigger reexecution
        Log->d("  LINE_MOVE STORE PENDING\n");
        return false;
    }
    Log->d("  LINE_MOVE DONE\n");
    return true;
}

char* Processor::appendSymbolInformation(char* index, VA addr, int padding)
{
#ifdef __linux__
    Dl_info info;
    int result = dladdr(ADDR_VOIDSTAR(addr), &info);
    if (result) {
        while (padding > 0) {
            *index = ' ';
            index++;
            padding--;
        }
        index += sim_sprintf(index, "| ");
        if (info.dli_fname) {
            index += sim_sprintf(index, "%s", info.dli_fname);
        }
        if (info.dli_sname) {
            index += sim_sprintf(index, "(%s)", info.dli_sname);
        }
        int delta = addr - ((uint32)info.dli_saddr);
        if (delta >= 0) {
            index += sim_sprintf(index, "+");
        }
        index += sim_sprintf(index, "%d", delta);
    }
#else // __linux__
#endif // __linux__
    return index;
}

/******************************************************************************
 * Printing info
 */

/*
 * Format an integer with commas inserted as thousands-separators.
 * Caller must pass in a string with enough room to format the
 * number including the commas.  The address of this string is
 * returned, so that this routine can be called as a function.
 *
 * (AIX's printf() claims to do this kind of formatting, but it doesn't work.)
 */
inline char *PunctuateLargeInteger (char *str, int64 val)
{
    sim_sprintf(str, "%lld", (long long) val);
    int i = strlen (str);
    int j = (i - 1) / 3; /* number of commas to add */
    str[i + j] = '\0';
    while (j) {
        str[i + j - 1] = str[i - 1];
        str[i + j - 2] = str[i - 2];
        str[i + j - 3] = str[i - 3];
        str[i + j - 4] = ',';
        i -= 3;
        j -= 1;
    }
    return str; /* return the string */
}

/******************************************************************************
 * PrintHeartbeat
 *
 * This is called from with SimpleCPURun
 * Reassure the user that something's happening every "HeartBeat" instructions.
 */
void Processor::printHeartbeat ()
{
    if (hb_print) {
        if (statInstructionsExecuted->peek() >=
            last_inst + hb_ival)
        {
            uint64 curr_inst = statInstructionsExecuted->peek();
            time_t curr_sec = time(NULL);

            /*
             * Format the "instructions executed" number with commas
             * to make it more readable
             */
            char buf[32];
            PunctuateLargeInteger (buf, curr_inst);

            /*
             * Compute elapsed real time, for display in a format
             * such as "6m 27s elapsed"
             */
            int ss = curr_sec - global_sim->getStartTime()->tv_sec;
            int mm =  ss / 60;
            ss -= mm * 60;
            int hh =  mm / 60;
            mm -= hh * 60;
            int dd =  hh / 24;
            hh -= dd * 24;
            char mins[16];
            if (mm)
                sim_sprintf(mins, "%dm ", mm);
            else
                mins[0] = '\0';
            char hours[16];
            if (hh)
                sim_sprintf(hours, "%dh ", hh);
            else
                hours[0] = '\0';
            char days[16];
            if (dd)
                sim_sprintf(days, "%dd ", dd);
            else
                days[0] = '\0';

            Log->i("%d:%lld:%.16llX HEARTBEAT %s instrs, %s%s%s%ds elapsed, PC = %.9llX",
                   getID(), global_sim->getSimclock(), cpu->getPC(),
                   buf, days, hours, mins, ss, cpu->getPC());
            cpu->printHeartbeat_extra();
            Log->i("\n");

            last_inst = curr_inst;
        }
    }
}

void Processor::printIPS () {
    if (ips_print) {
        time_t first_sec = global_sim->getStartTime()->tv_sec;
        if (last_ips_sec == 0) {
            last_ips_sec = first_sec;
        }
        time_t curr_sec = time(NULL);
        time_t tdiff = curr_sec - last_ips_sec;
        if (tdiff > (time_t)ips_ival) {
            uint64 curr_inst = statInstructionsExecuted->peek();
            uint ips =  (curr_inst - last_ips_inst) / tdiff;
            uint avg_ips = curr_inst / (curr_sec - first_sec);
            Log->i("%d:%lld:%.16llX IPS %d (%d avg)\n",
                   getID(), global_sim->getSimclock(), cpu->getPC(),
                   ips, avg_ips);
            last_ips_sec = curr_sec;
            last_ips_inst = curr_inst;
        }
    }
}

void Processor::sampleState (Bucket* b) {
    if ((sample_delta > -1) && 
        (global_sim->getSimclock() % sample_delta == 0)) {
        Log->i("SAMP::%lli::%i::%s\n",
               global_sim->getSimclock(),
               getID(),
               b->getName());
    }
}

void Processor::hookElect (Bucket* b) {
    b->elect();
}

void Processor::hookWordAccess () {
    // a hook to be overloaded by a child class
}

void Processor::hookWordAccessSuccess (WordAccessType type) {
    // a hook to be overloaded by a child class
}

bool Processor::getMemory (VA vAddr, uint nbytes, uint8* buf) {
    // send rawmemoryrequests for all the bytes required...
    uint   blocksize      = global_sim->cfg.blocksize;
    VA     currentAddress = vAddr;
    VA     blockAddress   = global_sim->blockAddr(vAddr);
    uint8* curBuf         = buf;

    Log->d("Processor::getMemory(vAddr=%llx, nbytes=%d, buf=%p)\n",
           vAddr, nbytes, buf);

    // jump point if gdb requested a bad address.
    if (sigsetjmp(gdbSIGSEGVJumpBuffer, 1) != 0) {
        // siglongjmp() was called.
        return false;
    }

    uint numCacheLines = nbytes / blocksize;
    if (nbytes % blocksize != 0) {
        numCacheLines++;
    }

    bool firstLine = true;
    for (uint bytesLeft = nbytes; bytesLeft != 0;) {
        uint bytes = blocksize - (currentAddress - blockAddress);
        if (bytes > bytesLeft) {
            bytes = bytesLeft;
        }
        RawMemoryRequest* rmr = NEW RawMemoryRequest(this,
                                                     global_sim->getMemory(),
                                                     blockAddress);
        routePacket(rmr); // this is instantaneous
        size_t offset = (firstLine ?
                         currentAddress - blockAddress : 
                         0);
        ASSERT(0 <= offset && offset < blocksize);
        uint8* data = (uint8*)rmr->getData();
        uint8* src =  data + offset;
        memcpy(curBuf, src, bytes);
        Log->d("Processor::getMemory blockAddress=%llx, curBuf=%p, src=%p, offset=%d, bytes=%d\n",
               blockAddress, curBuf, src, offset, bytes);
        for (uint i = offset; i < blocksize; i+=WORD_SIZE) {
            Log->d("\t*%llx=%x\n",
                   blockAddress+i, 
                   *(uint32*)(data+i));
        }

        firstLine       = false;
        curBuf         += bytes;
        currentAddress += bytes;
        bytesLeft      -= bytes;
        blockAddress   += global_sim->cfg.blocksize;

        delete rmr;
    }
    return true;
}

void Processor::printStatisticsCollection (FILE* statFile) {
    statistics->printStatisticsCollection(statFile, "");
}

void Processor::hookScheduleThreadSignal (int procID,
                                          int signum,
                                          SigContext* sigContext)
{
}

void Processor::clearInstructionState () {
    cpu->clearInstructionState();
}


